Skip to content

Post-merge-review: Fix template-no-action false positive in GJS/GTS#2677

Open
johanrd wants to merge 3 commits intoember-cli:masterfrom
johanrd:night_fix/template-no-action
Open

Post-merge-review: Fix template-no-action false positive in GJS/GTS#2677
johanrd wants to merge 3 commits intoember-cli:masterfrom
johanrd:night_fix/template-no-action

Conversation

@johanrd
Copy link
Copy Markdown
Contributor

@johanrd johanrd commented Apr 13, 2026

What's broken on master

action is an ambient strict-mode keyword in Ember (registered in STRICT_MODE_KEYWORDS at @ember/template-compiler/lib/plugins/index.ts), so {{action this.x}} works in .gjs/.gts templates without an import. The rule correctly flags the ambient keyword in both modes, but it doesn't account for users who legitimately shadow action with a JS binding or a template block param:

import action from './my-action-helper';
<template>{{action this.handleClick}}</template>   // false-positive on master
<template>
  {{#each items as |action|}}
    {{action this.x}}    {/* false-positive on master — `action` is the iterator */}
  {{/each}}
</template>

Fix

Add the JS-scope-walk + block-param tracking pattern already established in template-no-log, template-no-unbound, template-no-restricted-invocations, etc.:

  • Walk sourceCode.getScope().variables to detect JS bindings named action.
  • Push/pop GlimmerBlockStatement.program.blockParams and GlimmerElementNode.blockParams to track template-side block params.
  • Skip the report when action resolves to a local binding (either source).

The existing @action and this.action head-type guards are unchanged (those are JS-side namespaces, not the template keyword).

Test plan

22 tests pass on the branch (was 8). New cases:

  • 5 valid GJS/GTS cases covering JS-scope shadowing (import, const, mustache/sub-expression/modifier positions, both extensions)
  • 4 valid cases covering template block-param shadowing ({{#each as |action|}}, {{#let as |action|}}, modifier inside block, element-level <Foo as |action|>)
  • 3 invalid GJS/GTS cases confirming the ambient keyword is still flagged when not shadowed
  • 1 invalid case confirming that an unrelated JS import (import handler from './handler') does NOT mask the rule
  • 1 invalid case confirming that ambient action outside a block-param scope is still flagged even when a sibling block legitimately shadows it

Cowritten by claude

@johanrd johanrd marked this pull request as ready for review April 13, 2026 11:19
}

return {
GlimmerBlockStatement(node) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we dont need to do this -- scope is already managed by the parser

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks, fixing that for the others as well, now

@johanrd johanrd force-pushed the night_fix/template-no-action branch from d8426e4 to d77c61c Compare April 13, 2026 14:38
@johanrd johanrd marked this pull request as draft April 13, 2026 14:41
`action` is an ambient strict-mode keyword in Ember (registered in
STRICT_MODE_KEYWORDS at @ember/template-compiler/lib/plugins/index.ts),
so `{{action this.x}}` works in .gjs/.gts templates without an import.
The rule should still flag those uses — its purpose is to discourage
the deprecated keyword everywhere — but skip cases where `action`
resolves to a JS-scope binding or a template block param:

- `import action from './my-action-helper'; {{action this.x}}` (user import)
- `const action = (h) => () => h(); {{action this.x}}` (local declaration)
- `{{#each items as |action|}}{{action this.x}}{{/each}}` (block-param shadow)
- `<Foo as |action|>{{action this.x}}</Foo>` (element-block-param shadow)

Add the same JS-scope-walk + block-param tracking pattern used by
template-no-log, template-no-unbound, etc.: walk
sourceCode.getScope().variables for the JS side, and push/pop
GlimmerBlockStatement.program.blockParams and GlimmerElementNode.blockParams
for template-side scopes. The `@action` and `this.action` head-type checks
are unchanged (those are JS-side namespaces, not the template keyword).

Tests: 22 (was 8) — adds 9 new valid cases (5 JS-scope, 4 block-param) and
5 new invalid cases (3 ambient-keyword in .gjs/.gts, 1 unrelated import
does not mask, 1 ambient-after-block-exit).

Docs updated to document the strict-mode behavior.
@johanrd johanrd force-pushed the night_fix/template-no-action branch from d77c61c to 003543c Compare April 13, 2026 14:45
@johanrd johanrd marked this pull request as ready for review April 13, 2026 14:53

## Strict-mode behavior

`action` is an ambient strict-mode keyword in Ember (registered in `STRICT_MODE_KEYWORDS`), so `{{action this.x}}` works in `.gjs`/`.gts` templates without an explicit import. The rule still flags those uses to discourage the deprecated keyword — but skips reports when `action` resolves to a JS-scope binding (an import or local declaration) or a template block param.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it was also removed in ember 6? can you double check that? so for users of ember-source 6+ we could provide a more specific error

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

added a more specific error now

johanrd added 2 commits April 13, 2026 20:36
{{action}} was deprecated in Ember 5.9 and removed in 6.0. Mention
this in all three message variants so users know the timeline without
having to look it up.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants